#ifndef CPLOG_H
#define CPLOG_H
#include <TClass.h>
#include <TObjArray.h>
#include <TObject.h>
#include <TString.h>

// deprecation macro
#if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
#define COLDPIG_DEPRECATED(func) func  __attribute__ ((deprecated))
#elif defined(_MSC_VER) && _MSC_VER >= 1300
#define COLDPIG_DEPRECATED(func) __declspec(deprecated) func
# else
#define COLDPIG_DEPRECATED(func) func
#endif

/**
 * class for logging debug, info and error messages
 */
class CpLog: public TObject {
public:

   // Log4j log levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
   enum EType_t {kFatal = 0, kError, kWarning, kInfo, kDebug, kMaxType};
   typedef void (*CpLogNotification)(EType_t type, const char* message);

   // NB: singleton constructor & destructor should not be public!
   // COLDPIG_DEPRECATED(CpLog());
   // COLDPIG_DEPRECATED(virtual ~CpLog());

   // NB: singleton deprecated static instance method
   // COLDPIG_DEPRECATED(static CpLog* Instance() {return fgInstance;};)

   // get root logger singleton instance
   static CpLog *GetRootLogger();

   // delete root logger singleton instance
   static void DeleteRootLogger();

   // NB: the following functions should not be static
   // NB: deprecated: logging configuration should be made through to a configuration file
   static void  EnableDebug(Bool_t enabled);
   static void  SetGlobalLogLevel(EType_t type);
   static Int_t GetGlobalLogLevel();
   static void  SetGlobalDebugLevel(Int_t level);
   static Int_t GetGlobalDebugLevel();
   static void  SetModuleDebugLevel(const char* module, Int_t level);
   static void  ClearModuleDebugLevel(const char* module);
   static void  SetClassDebugLevel(const char* className, Int_t level);
   static void  ClearClassDebugLevel(const char* className);

   static void  SetStandardOutput();
   static void  SetStandardOutput(EType_t type);
   static void  SetErrorOutput();
   static void  SetErrorOutput(EType_t type);
   static void  SetFileOutput(const char* fileName);
   static void  SetFileOutput(EType_t type, const char* fileName);
   static void  SetStreamOutput(ostream* stream);
   static void  SetStreamOutput(EType_t type, ostream* stream);
   static void  SetLogNotification(CpLogNotification pCallBack);
   static void  SetLogNotification(EType_t type, CpLogNotification pCallBack);
   static void  Flush();

   static void  SetHandleRootMessages(Bool_t on);

   static void  SetPrintType(Bool_t on);
   static void  SetPrintType(EType_t type, Bool_t on);
   static void  SetPrintModule(Bool_t on);
   static void  SetPrintModule(EType_t type, Bool_t on);
   static void  SetPrintScope(Bool_t on);
   static void  SetPrintScope(EType_t type, Bool_t on);
   static void  SetPrintLocation(Bool_t on);
   static void  SetPrintLocation(EType_t type, Bool_t on);

   static void  SetPrintRepetitions(Bool_t on);

   static void  WriteToFile(const char* name, Int_t option = 0);

   // the following public methods are used by the preprocessor macros
   // and should not be called directly
   static Bool_t IsDebugEnabled() {return fgDebugEnabled;}
   static Int_t GetDebugLevel(const char* module, const char* className);
   static void  Message(UInt_t level, const char* message,
                        const char* module, const char* className,
                        const char* function, const char* file, Int_t line);
   static void  Debug(UInt_t level, const char* message,
                      const char* module, const char* className,
                      const char* function, const char* file, Int_t line);

   static Int_t RedirectStdoutTo(EType_t type, UInt_t level, const char* module,
                                 const char* className, const char* function,
                                 const char* file, Int_t line, Bool_t print);
   static Int_t RedirectStderrTo(EType_t type, UInt_t level, const char* module,
                                 const char* className, const char* function,
                                 const char* file, Int_t line, Bool_t print);
   static void  RestoreStdout(Int_t original);
   static void  RestoreStderr(Int_t original);

   static ostream& Stream(EType_t type, UInt_t level,
                          const char* module, const char* className,
                          const char* function, const char* file, Int_t line);

private:

   // constructor is made private for implementing a singleton
   CpLog();
   virtual ~CpLog();

   // NOT IMPLEMENTED?
   CpLog(const CpLog& log);
   CpLog& operator = (const CpLog& log);

   void           ReadEnvSettings();

   static void    RootErrorHandler(Int_t level, Bool_t abort,
                                   const char* location, const char* message);

   void           CloseFile(Int_t type);
   FILE*          GetOutputStream(Int_t type);

   UInt_t         GetLogLevel(const char* module, const char* className) const;
   void           PrintMessage(UInt_t type, const char* message,
                               const char* module, const char* className,
                               const char* function,
                               const char* file, Int_t line);

   void           PrintString(Int_t type, FILE* stream, const char* format, ...);
   void           PrintRepetitions();

   Int_t          RedirectTo(FILE* stream, EType_t type, UInt_t level,
                             const char* module, const char* className,
                             const char* function,
                             const char* file, Int_t line, Bool_t print);

   ostream&       GetStream(EType_t type, UInt_t level,
                            const char* module, const char* className,
                            const char* function, const char* file, Int_t line);

   enum {kDebugOffset = kDebug - 1};

   static CpLog* fgInstance;                 //! pointer to current instance
   static Bool_t  fgDebugEnabled;             // flag for debug en-/disabling

   UInt_t         fGlobalLogLevel;            // global logging level
   TObjArray      fModuleDebugLevels;         // debug levels for modules
   TObjArray      fClassDebugLevels;          // debug levels for classes

   Int_t          fOutputTypes[kMaxType];     // types of output streams
   TString        fFileNames[kMaxType];       // file names
   FILE*          fOutputFiles[kMaxType];     //! log output files
   ostream*       fOutputStreams[kMaxType];   //! log output streams

   Bool_t         fPrintType[kMaxType];       // print type on/off
   Bool_t         fPrintModule[kMaxType];     // print module on/off
   Bool_t         fPrintScope[kMaxType];      // print scope/class name on/off
   Bool_t         fPrintLocation[kMaxType];   // print file and line on/off

   Bool_t         fPrintRepetitions;          // print number of repetitions instead of repeated message on/off

   Int_t          fRepetitions;               //! counter of repetitions
   UInt_t         fLastType;                  //! type of last message
   TString        fLastMessage;               //! last message
   TString        fLastModule;                //! module name of last message
   TString        fLastClassName;             //! class name of last message
   TString        fLastFunction;              //! function name of last message
   TString        fLastFile;                  //! file name of last message
   Int_t          fLastLine;                  //! line number of last message
   CpLogNotification fCallBacks[kMaxType];   //! external notification callback

   ClassDef(CpLog, 1)   // class for logging debug, info and error messages
};


// module name macro
#ifdef _MODULE_
#define MODULENAME() _MODULE_
#else
#define MODULENAME() "NoModule"
#endif

// function name macro
#if defined(__GNUC__) || defined(__ICC) || defined(__ECC) || defined(__APPLE__)
#define FUNCTIONNAME() __FUNCTION__
// #elif defined(__HP_aCC) || defined(__alpha) || defined(__DECCXX)
// #define FUNCTIONNAME() __FUNC__
#else
#define FUNCTIONNAME() "???"
#endif

// redirection
#define REDIRECTSTDOUT(type, level, scope, whatever) {Int_t originalStdout = CpLog::RedirectStdoutTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); whatever; CpLog::RestoreStdout(originalStdout);}
#define REDIRECTSTDERR(type, level, scope, whatever) {Int_t originalStderr = CpLog::RedirectStderrTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); whatever; CpLog::RestoreStderr(originalStderr);}
#define REDIRECTSTDOUTANDSTDERR(type, level, scope, whatever) {Int_t originalStdout = CpLog::RedirectStdoutTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); Int_t originalStderr = CpLog::RedirectStderrTo(type, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__, kFALSE); whatever; CpLog::RestoreStderr(originalStderr); CpLog::RestoreStdout(originalStdout);}


// debug level
#ifdef LOG_NO_DEBUG
#define CpDebugLevel() -1
#define CpDebugLevelClass() -1
#define CpDebugLevelGeneral(scope) -1
#else
#define CpDebugLevel() ((CpLog::IsDebugEnabled()) ? CpLog::GetDebugLevel(MODULENAME(), ClassName()) : -1)
#define CpDebugLevelClass() ((CpLog::IsDebugEnabled()) ? CpLog::GetDebugLevel(MODULENAME(), Class()->GetName()) : -1)
#define CpDebugLevelGeneral(scope) ((CpLog::IsDebugEnabled()) ? CpLog::GetDebugLevel(MODULENAME(), scope) : -1)
#endif

// debug messages
#ifdef LOG_NO_DEBUG
#define CpDebug(level, message)
#define CpDebugClass(level, message)
#define CpDebugGeneral(scope, level, message)
#else

// inspired by log4cxx code (see log4cxx/Logger.h)
// implements GCC branch prediction for increasing logging performance
#if !defined(COLDPIG_UNLIKELY)
#if defined(__GNUC__) && (__GNUC__ >= 3)
/**
Provides optimization hint to the compiler
to optimize for the expression being false.
@param expr boolean expression.
@returns value of expression.
*/
#define COLDPIG_UNLIKELY(expr) __builtin_expect(expr, 0)
#else
/**
Provides optimization hint to the compiler
to optimize for the expression being false.
@param expr boolean expression.
@returns value of expression.
**/
#define COLDPIG_UNLIKELY(expr) expr
#endif
#endif

/**
Logs a message to a specified logger with the DEBUG level.

@param logLevel the debug level.
@param message message to print in the following format: Form(message).
    Note, that message should contain balanced parenthesis, like
    <code>CpDebug(1, Form("Failed to decode line %d of %s", line, filename));</code>
*/
#define CpDebug(logLevel, message) \
   do { if (COLDPIG_UNLIKELY(CpLog::IsDebugEnabled() && CpLog::GetDebugLevel(MODULENAME(), ClassName()) >= logLevel)) {\
         CpLog::Debug(logLevel, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__); }} while (0)

/**
Logs a message to a specified logger with the DEBUG level.

@param logLevel the debug level.
@param message message to print in the following format: Form(message).
    Note, that message should contain balanced parenthesis, like
    <code>CpDebug(1, Form("Failed to decode line %d of %s", line, filename));</code>
*/
#define CpDebugClass(logLevel, message) \
   do { if (COLDPIG_UNLIKELY(CpLog::IsDebugEnabled() && CpLog::GetDebugLevel(MODULENAME(), Class()->GetName()) >= logLevel)) {\
         CpLog::Debug(logLevel, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__); }} while (0)

/**
Logs a message to a specified logger with the DEBUG level.

@param scope the logging scope.
@param logLevel the debug level.
@param message message to print in the following format: Form(message).
    Note, that message should contain balanced parenthesis, like
    <code>CpDebug(1, Form("Failed to decode line %d of %s", line, filename));</code>
*/
#define CpDebugGeneral(scope, logLevel, message) \
   do { if (COLDPIG_UNLIKELY(CpLog::IsDebugEnabled() && CpLog::GetDebugLevel(MODULENAME(), scope) >= logLevel)) {\
         CpLog::Debug(logLevel, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__); }} while (0)

#endif

// redirection to debug
#define StdoutToCpDebug(level, whatever) REDIRECTSTDOUT(CpLog::kDebug, level, ClassName(), whatever)
#define StderrToCpDebug(level, whatever) REDIRECTSTDERR(CpLog::kDebug, level, ClassName(), whatever)
#define ToCpDebug(level, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kDebug, level, ClassName(), whatever)
#define StdoutToCpDebugClass(level, whatever) REDIRECTSTDOUT(CpLog::kDebug, level, Class()->GetName(), whatever)
#define StderrToCpDebugClass(level, whatever) REDIRECTSTDERR(CpLog::kDebug, level, Class()->GetName(), whatever)
#define ToCpDebugClass(level, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kDebug, level, Class()->GetName(), whatever)
#define StdoutToCpDebugGeneral(scope, level, whatever) REDIRECTSTDOUT(CpLog::kDebug, level, scope, whatever)
#define StderrToCpDebugGeneral(scope, level, whatever) REDIRECTSTDERR(CpLog::kDebug, level, scope, whatever)
#define ToCpDebugGeneral(scope, level, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kDebug, level, scope, whatever)

// debug stream objects
#define CpDebugStream(level) CpLog::Stream(CpLog::kDebug, level, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpDebugClassStream(level) CpLog::Stream(CpLog::kDebug, level, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpDebugGeneralStream(scope, level) CpLog::Stream(CpLog::kDebug, level, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)


// info messages
#ifdef LOG_NO_INFO
#define CpInfo(message)
#define CpInfoClass(message)
#define CpInfoGeneral(scope, message)
#else
#define CpInfo(message) {CpLog::Message(CpLog::kInfo, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpInfoClass(message) {CpLog::Message(CpLog::kInfo, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpInfoGeneral(scope, message) {CpLog::Message(CpLog::kInfo, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}
#endif

// redirection to info
#define StdoutToCpInfo(whatever) REDIRECTSTDOUT(CpLog::kInfo, 0, ClassName(), whatever)
#define StderrToCpInfo(whatever) REDIRECTSTDERR(CpLog::kInfo, 0, ClassName(), whatever)
#define ToCpInfo(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kInfo, 0, ClassName(), whatever)
#define StdoutToCpInfoClass(whatever) REDIRECTSTDOUT(CpLog::kInfo, 0, Class()->GetName(), whatever)
#define StderrToCpInfoClass(whatever) REDIRECTSTDERR(CpLog::kInfo, 0, Class()->GetName(), whatever)
#define ToCpInfoClass(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kInfo, 0, Class()->GetName(), whatever)
#define StdoutToCpInfoGeneral(scope, whatever) REDIRECTSTDOUT(CpLog::kInfo, 0, scope, whatever)
#define StderrToCpInfoGeneral(scope, whatever) REDIRECTSTDERR(CpLog::kInfo, 0, scope, whatever)
#define ToCpInfoGeneral(scope, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kInfo, 0, scope, whatever)

// info stream objects
#define CpInfoStream() CpLog::Stream(CpLog::kInfo, 0, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpInfoClassStream() CpLog::Stream(CpLog::kInfo, 0, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpInfoGeneralStream(scope) CpLog::Stream(CpLog::kInfo, 0, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)

// warning messages
#ifdef LOG_NO_WARNING
#define CpWarning(message)
#define CpWarningClass(message)
#define CpWarningGeneral(scope, message)
#else
#define CpWarning(message) {CpLog::Message(CpLog::kWarning, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpWarningClass(message) {CpLog::Message(CpLog::kWarning, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpWarningGeneral(scope, message) {CpLog::Message(CpLog::kWarning, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}
#endif

// redirection to warning
#define StdoutToCpWarning(whatever) REDIRECTSTDOUT(CpLog::kWarning, 0, ClassName(), whatever)
#define StderrToCpWarning(whatever) REDIRECTSTDERR(CpLog::kWarning, 0, ClassName(), whatever)
#define ToCpWarning(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kWarning, 0, ClassName(), whatever)
#define StdoutToCpWarningClass(whatever) REDIRECTSTDOUT(CpLog::kWarning, 0, Class()->GetName(), whatever)
#define StderrToCpWarningClass(whatever) REDIRECTSTDERR(CpLog::kWarning, 0, Class()->GetName(), whatever)
#define ToCpWarningClass(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kWarning, 0, Class()->GetName(), whatever)
#define StdoutToCpWarningGeneral(scope, whatever) REDIRECTSTDOUT(CpLog::kWarning, 0, scope, whatever)
#define StderrToCpWarningGeneral(scope, whatever) REDIRECTSTDERR(CpLog::kWarning, 0, scope, whatever)
#define ToCpWarningGeneral(scope, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kWarning, 0, scope, whatever)

// warning stream objects
#define CpWarningStream() CpLog::Stream(CpLog::kWarning, 0, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpWarningClassStream() CpLog::Stream(CpLog::kWarning, 0, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpWarningGeneralStream(scope) CpLog::Stream(CpLog::kWarning, 0, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)


// error messages
#define CpError(message) {CpLog::Message(CpLog::kError, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpErrorClass(message) {CpLog::Message(CpLog::kError, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpErrorGeneral(scope, message) {CpLog::Message(CpLog::kError, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}

// redirection to error
#define StdoutToCpError(whatever) REDIRECTSTDOUT(CpLog::kError, 0, ClassName(), whatever)
#define StderrToCpError(whatever) REDIRECTSTDERR(CpLog::kError, 0, ClassName(), whatever)
#define ToCpError(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kError, 0, ClassName(), whatever)
#define StdoutToCpErrorClass(whatever) REDIRECTSTDOUT(CpLog::kError, 0, Class()->GetName(), whatever)
#define StderrToCpErrorClass(whatever) REDIRECTSTDERR(CpLog::kError, 0, Class()->GetName(), whatever)
#define ToCpErrorClass(whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kError, 0, Class()->GetName(), whatever)
#define StdoutToCpErrorGeneral(scope, whatever) REDIRECTSTDOUT(CpLog::kError, 0, scope, whatever)
#define StderrToCpErrorGeneral(scope, whatever) REDIRECTSTDERR(CpLog::kError, 0, scope, whatever)
#define ToCpErrorGeneral(scope, whatever) REDIRECTSTDOUTANDSTDERR(CpLog::kError, 0, scope, whatever)

// error stream objects
#define CpErrorStream() CpLog::Stream(CpLog::kError, 0, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpErrorClassStream() CpLog::Stream(CpLog::kError, 0, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__)
#define CpErrorGeneralStream(scope) CpLog::Stream(CpLog::kError, 0, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__)


// fatal messages
#define CpFatal(message) {CpLog::Message(CpLog::kFatal, message, MODULENAME(), ClassName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpFatalClass(message) {CpLog::Message(CpLog::kFatal, message, MODULENAME(), Class()->GetName(), FUNCTIONNAME(), __FILE__, __LINE__);}
#define CpFatalGeneral(scope, message) {CpLog::Message(CpLog::kFatal, message, MODULENAME(), scope, FUNCTIONNAME(), __FILE__, __LINE__);}

#endif
